#include "spu.h"
#include "list_operate.h"
#include <semaphore.h>
#include "cJSON.h"

#define   max_size    1024
Client_Node *head;  //客户端头结点
User_Node *u_head;  //已注册用户头结点
Online_User_Node *olu_head;  //在线用户头结点

//存储用户上传json数据
char type[20];
char name[20];
char passwd[20];
char chat_all_data[1024];
int online_connfd;

int current_client; //当前发消息的客户端套接字
int sockfd;         //处理链接的套接字
sem_t sem;          //定义信号量
char menu_buff[max_size];   //用于存储所有客户端发送来的数据


//群聊json数对象
cJSON *cJSON_Chat_All =  NULL;
//新建存放在线用户名json对象，
cJSON* cJSON_Online_User = NULL;
//新建一个数组类型的的json对象
// cJSON* cJSON_user_arr = NULL;

//用户文件读写
FILE *fp_write;
FILE *fp_read;



// void del_cJSON_array_item(cJSON *root, char *value_to_delete)
// {
//     int delete_index = -1; // 要删除的元素下标，初始化为 -1

//     int array_size = cJSON_GetArraySize(root); // 获取数组大小
//     for (int i = 0; i < array_size; i++) {
//         cJSON *item = cJSON_GetArrayItem(root, i); // 获取数组元素
//         if (cJSON_IsNumber(item) && item->valueint == value_to_delete) {
//             delete_index = i; // 找到要删除的元素，记录下标
//             break;
//         }
//     }

//     if (delete_index >= 0) {
//         cJSON_DeleteItemFromArray(root, delete_index); // 删除元素
//     }

//     char *new_json_string = cJSON_Print(root); // 重新生成 JSON 字符串
//     printf("array = %s\n", new_json_string);

//     cJSON_Delete(root); // 释放 cJSON 节点内存
// }

/*客户端线程*/
void *client_fun(void *arg)
{
    int connfd = (long int)arg;
    //接受客户端数据缓冲区
    char buff[max_size];
    memset(buff, 0, sizeof(buff));
    
    //套接字发送给客户端
    char sd[20] = {0};
    sprintf(sd, "connfd:%d", connfd);
    send(connfd, sd, sizeof(sd), 0);

    for(;;)
    {
        int recv = read(connfd, buff, sizeof(buff));
        if(recv > 0){
            //在数据前面加上客户端名字,将客户端发送数据拷贝到menu_buff
            sprintf(menu_buff, "%d:%s",connfd, buff);
            // printf("I am %d, recieve data = %s\n", connfd, buff);
            //信号量+1，通知菜单线程处理客户端数据
            sem_post(&sem);  //信号量+1
            memset(buff, 0, sizeof(buff));
        }
        //如果返回是0，说明链接可能断开
        if(recv == 0){
            //关闭链接
            if(0 == close(connfd)){
                printf("I am %d, closed\n", connfd);
                //删除在线用户结点
                del_list_online_user(olu_head, connfd);
                /*==================发送在线用户给所有在线用户===============*/
                cJSON_DeleteItemFromObject(cJSON_Online_User,"online_users");
                //将当前用户名添加进json对象
                //存放在线用户名的数组类型的json对象
                cJSON* cJSON_user_arr = NULL;
                cJSON_user_arr = cJSON_CreateArray();
                cJSON_DeleteItemFromObject(cJSON_Online_User,"online_users");
                cJSON_AddItemToObject(cJSON_Online_User, "online_users", cJSON_user_arr);
                Online_User_Node *p = olu_head->next;
                while(p != NULL)
                {
                    //将用户名添加进json item数组里
                    cJSON_AddItemToArray(cJSON_user_arr,cJSON_CreateString(p->name));
                    p = p->next;
                }
                /* 打印JSON对象(整条链表)的所有数据 */
                char* str = NULL;
                str = cJSON_Print(cJSON_Online_User);
                //转发数据所有在线客户端
                p = olu_head->next;
                while(p != NULL)
                {
                    send(p->connfd, str, strlen(str), 0);
                    p = p->next;
                }
            }  
            del_list_client(head, connfd);  //删除用户结点
            //退出线程
            pthread_exit(NULL);
        }
    }   
}

/*菜单线程*/
void * menu_thread(void *arg)
{
    for(;;)
    {
        //等待信号量大于0，等到后对信号量”减1“
        if(0 == sem_wait(&sem))
        {
            //截取出当前发消息的客户端套接字
            char temp_menu_buff[10];
            memcpy(temp_menu_buff, menu_buff, 10);
            current_client = atoi(strtok(temp_menu_buff, ":"));

            /****处理客户端数据****/
            /*解析JSON数据包,使用该函数会通过malloc()函数在内存中开辟一个空间，
              使用完成需要手动释放。*/
            cJSON *root=cJSON_Parse((const char *)&menu_buff[2]);  
            if(root){
                 /*从cJSON结构体中查找某个子节点名称（键名称）
                如果查找成功可把该子节点序列化到cJSON结构体中*/
                cJSON *item=cJSON_GetObjectItem(root,"type");  
                if(item){
                    memset(type, 0, sizeof(type));
                    memcpy(type, item->valuestring, strlen(item->valuestring));

                    /*判断是否是注册信息*/
                    if(strcmp(type, "regist") == 0){
                        printf("注册用户\n");
                        memset(name, 0, sizeof(name));
                        memset(passwd, 0, sizeof(passwd));

                        //获取用户名
                        item=cJSON_GetObjectItem(root,"name");
                        if(item){
                            memcpy(name, item->valuestring, strlen(item->valuestring));
                            printf("name = %s\n",name);
                            User_Node *p = u_head->next;
                            for( ; p != NULL ; )
                            {
                                if(strcmp(p->name, name) == 0){
                                    printf("注册失败，用户已存在！\n");
                                    //回复客户端
                                    send(current_client, "regist user exist", 20, 0);
                                    break;
                                }
                                p = p->next;
                            }
                            //如果用户不存在则开始注册
                            if(p == NULL){
                                printf("开始注册用户: %s\n", name);
                                item=cJSON_GetObjectItem(root,"passwd");
                                if(item){
                                    memcpy(passwd, item->valuestring, strlen(item->valuestring));
                                    printf("passwd = %s\n",passwd);
                                    
                                    //向文件中写入用户结点
                                    char *strBuf=(char*)malloc(40);
                                    sprintf(strBuf, "%s %s\n", name, passwd);
                                    // printf("注册数据 = %s", strBuf);
                                    fp_write=0;
                                    if((fp_write=fopen("/home/hc/Linux_ARM/Linux多线程/13.2聊天室(注册登陆) /user.txt","a"))==0)
                                    {
                                        printf("文件打开失败!\n");
                                    }else{
                                        //写入文件数据
                                        if(fprintf(fp_write,"%s",strBuf) > 0){
                                            //向链表中添加用户结点
                                            add_list_user(u_head, name, passwd);
                                            printf("注册用户成功！开始聊天！\n");
                                            //回复客户端
                                            send(current_client, "regist ok", 20, 0);
                                        }
                                    }
                                    strBuf=NULL;
                                    fclose(fp_write);
                                }
                            }
                        }
                        
                    }

                    /*判断是否是登陆信息*/
                    if(strcmp(type, "ent") == 0){
                        printf("登陆用户\n");
                        memset(name, 0, sizeof(name));
                        memset(passwd, 0, sizeof(passwd));

                        //获取用户名
                        item=cJSON_GetObjectItem(root,"name");
                        if(item){
                            memcpy(name, item->valuestring, strlen(item->valuestring));
                            printf("name = %s\n",name);
                            User_Node *p = u_head->next;
                            for( ; p != NULL ; )
                            {
                                //如果用户存在则判断密码是否正确
                                if(strcmp(p->name, name) == 0){
                                    printf("开始登陆用户: %s\n", name);
                                    item=cJSON_GetObjectItem(root,"passwd");
                                    if(item){
                                        memcpy(passwd, item->valuestring, strlen(item->valuestring));
                                        sprintf(passwd, "%s", passwd);   //密码后面加个回车，保持跟真实密码的一致性
                                        // printf("正确的密码 %s, passwd = %s\n",  p->passwd,passwd);
                                        if(strcmp(p->passwd, passwd) == 0){
                                            printf("登陆成功！\n");
                                            //回复客户端，登陆成功的消息
                                            send(current_client, "ent ok", 20, 0);
                                            usleep(50000);   //延时50ms，防止客户端收不到在线用户列表信息
                                            //获取客户端发来的在服务器上对应的connfd
                                            item=cJSON_GetObjectItem(root,"connfd");
                                            if(item){
                                                //获取客户端发来的套接字(对应其在服务器上的套接字)
                                                online_connfd = item->valueint;
                                                //添加在线用户结点
                                                add_list_online_user(olu_head, online_connfd, name, passwd);

                                                /*==================发送在线用户给所有在线用户===============*/
                                                //将当前用户名添加进json对象
                                                //存放在线用户名的数组类型的json对象
                                                cJSON* cJSON_user_arr = NULL;
                                                cJSON_user_arr = cJSON_CreateArray();
                                                cJSON_DeleteItemFromObject(cJSON_Online_User,"online_users");
                                                cJSON_AddItemToObject(cJSON_Online_User, "online_users", cJSON_user_arr);
                                                Online_User_Node *p = olu_head->next;
                                                while(p != NULL)
                                                {
                                                    //将用户名添加进json item数组里
                                                    cJSON_AddItemToArray(cJSON_user_arr,cJSON_CreateString(p->name));
                                                    p = p->next;
                                                }
                                                /* 打印JSON对象(整条链表)的所有数据 */
                                                char* str = NULL;
                                                str = cJSON_Print(cJSON_Online_User);
                                                //转发数据所有在线客户端
                                                p = olu_head->next;
                                                printf("当前在线用户:\n");
                                                printf("id \t name \t connfd\n");
                                                int i = 1;
                                                while(p != NULL)
                                                {
                                                    send(p->connfd, str, strlen(str), 0);
                                                    printf("%d \t %s \t %d\n", i++, p->name, p->connfd);
                                                    p = p->next;
                                                }
                                                // cJSON_DeleteItemFromObject(cJSON_Online_User,"online_users");
                                                // cJSON_Delete(cJSON_user_arr);
                                            }
                                            //欢迎语，发送给客户端
                                            // char sd[] = "-----welcome to my server!-----";
                                            // send(current_client, sd, sizeof(sd), 0);
                                        }else{
                                            //回复客户端
                                            send(current_client, "passwd error", 20, 0);
                                            printf("密码不正确！\n");  
                                        }
                                    }
                                    break;
                                }
                                p = p->next;
                            }
                            // 用户不存在
                            if(p == NULL){
                                //回复客户端
                                send(current_client, "user not exist", 20, 0);
                                printf("用户不存在！\n");  
                            }
                        }
                    }

                    /*判断是否是群聊信息*/
                    if(strcmp(type, "chat_all") == 0){
                        memset(chat_all_data, 0, sizeof(chat_all_data));

                        //获取用户名
                        memset(name, 0, sizeof(name));
                        item=cJSON_GetObjectItem(root,"name");
                        if(item){
                            memcpy(name, item->valuestring, strlen(item->valuestring));
                            // printf("name = %s\n",name);
                        }
                        //获取聊天内容
                        item=cJSON_GetObjectItem(root,"data");
                        if(item){
                            char tempdata[1024];
                            memset(tempdata, 0, sizeof(tempdata));
                            memcpy(tempdata, item->valuestring, strlen(item->valuestring));
                            //将发送消息的客户端名字和内容存入缓冲区
                            sprintf(chat_all_data, "%s:%s", name, tempdata);
                            /* 添加一条字符串类型的JSON数据(添加一个链表节点) */
                            cJSON_AddStringToObject(cJSON_Chat_All, "type", "chat_all");     //数据类型:群聊
                            cJSON_AddStringToObject(cJSON_Chat_All, "data", chat_all_data);  //数据内容
                            memset(chat_all_data, 0, sizeof(chat_all_data));

                            /* 打印JSON对象(整条链表)的所有数据 */
                            char* str = NULL;
                            str = cJSON_Print(cJSON_Chat_All);
                            cJSON_Delete(cJSON_Chat_All);   //删除json对象，释放内存
                            cJSON_Chat_All = cJSON_CreateObject();   //重新初初始化json对象
                            //转发数据到其他的在线客户端
                            Online_User_Node *p = olu_head->next;
                            while(p != NULL)
                            {
                                send(p->connfd, str, strlen(str), 0);
                                p = p->next;
                            }
                        }
                    }
                }
                //释放cJSON_Parse()分配出来的内存空间。
                cJSON_Delete(root);
                memset(menu_buff, 0, sizeof(menu_buff));
            } 
        }

    }
}

/*检测到ctrl+c的信号函数*/
void close_fun()
{
    while(close(sockfd) == 0){
        sleep(1);
    }
    printf("\nstop tcpserver\n");
    exit(0);
}

int main()
{
    signal(SIGINT, close_fun);

    //群聊json数据对象
    cJSON_Chat_All = cJSON_CreateObject();   //每次都删除，释放内存
    //存放在线用户名的json数据对象
    cJSON_Online_User = cJSON_CreateObject();   //不用每次删除，只有用户登陆时添加，用户离线时删除item
    cJSON_AddStringToObject(cJSON_Online_User, "type", "online_users");     //数据类型:在线用户

    
    /*初始化信号量*/
    sem_init(&sem, 1, 0);

    /*1.创建套接字*/
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd == -1){
        printf("创建套接字失败!\n");
        return 0;
    }

    int mw_optval = 1;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&mw_optval,sizeof(mw_optval));


    /*2.绑定IP和端口,分配地址给套接字*/
    struct sockaddr_in addr;
    addr.sin_port = htons(6666);  //函数将无符号短整数hostshort从主机字节顺序转换为网络字节顺序。
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY:0.0.0.0，可以泛指本机的所有IP（多个网卡）
                                              //而127.0.0.1只能是服务器和客户端都在本机上才可以
    int res;
    res = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
    if(res == -1){
        printf("bind failed!\n");
        return 0;
    }

    /*3.监听链接请求*/
    if(listen(sockfd, 100) == -1){
        printf("listen failed!\n");
        return 0;
    }
    printf("*****服务器创建成功！*****\n");

    /*申请头结点*/
    head = (Client_Node *)malloc(sizeof(Client_Node));
    head->next = NULL;

    /*创建菜单线程*/
    pthread_t menu_thread_id;
    pthread_create(&menu_thread_id, NULL, menu_thread, NULL);

    /*读取当前已注册的用户信息*/
    /*申请用户头结点*/
    u_head = (User_Node *)malloc(sizeof(User_Node));
    u_head->next = NULL;

    /*申请在线用户头结点*/
    olu_head = (Online_User_Node *)malloc(sizeof(Online_User_Node));
    olu_head->next = NULL;

    /*读取用户文件*/         
    fp_read=0;
    //记得这里要换模式，改成只读
    if((fp_read=fopen("/home/hc/Linux_ARM/Linux多线程/13.2聊天室(注册登陆) /user.txt","r"))==0)
    {
        printf("文件打开失败!\n");
        return 0;
    }
    char strBuf[200];
    memset(strBuf,0,sizeof(strBuf));
    int i = 1;
    size_t len;
    printf("******所有已注册用户******\n");
    while(1)
    {
        if(fgets(strBuf,sizeof(strBuf),fp_read)==0)break;
        char *name = strtok(strBuf, " ");
        char *passwd = strtok(NULL, " ");
        // 将换行符替换为字符串结束符，否则密码最后会有一个换行符
        passwd[strlen(passwd)-1] = '\0';   
        //向链表中添加用户结点
        add_list_user(u_head, name, passwd);
        printf("%2d: name: %-10s,\t passwd: %s\n", i++,  name, passwd);
        memset(strBuf,0,sizeof(strBuf));
    }
    fclose(fp_read);
    
    /*4.循环等待客户端链接*/ 
    /*主线程接收客户端链接请求*/
    while(1){
        long int connfd;
        connfd = accept(sockfd, NULL, NULL);
        
        if(connfd != -1){ 
            printf("一个新客户端连接, connfd = %ld\n",connfd);
            /*向客户端链表中添加新用户结点*/
            add_list_client(head, connfd);
            
            /*创建新线程处理客户端数据*/
            pthread_t thread_id;
            pthread_create(&thread_id, NULL, client_fun, (void*)connfd);
        }
    }
    
    return 0;
}
